package core;

import core.anno.*;
import core.interceptor.Interceptor;
import core.utils.Handler;
import core.utils.StrUtils;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 核心类
 * @author wsz
 * @date 2021/10/28 10:25
 **/
public class DispatcherServlet extends HttpServlet {
    // 配置文件
    private Properties properties = new Properties();
    // 扫描包内的class
    private List<String> classNames = new ArrayList<>();
    // ioc容器，依赖注入
    private Map<String ,Object> iocMap = new HashMap<>();
    // 请求url与方法对应
    private List<Handler> handlerMapping = new ArrayList<>();
    // 拦截器：执行顺序1-2-3-invoke-3-2-1
    private List<Interceptor> interceptorList = new LinkedList<>();

    @Override
    public void init(ServletConfig config) {
        System.out.println("init start");

        // 1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        // 2.扫描
        doScan(properties.getProperty("scanPackage"));
        // 3.实例化
        doInstance();
        // 4.依赖注入
        doAutowired();
        // 5.HandlerMapping构建
        initHandlerMapping();
        // 6.获取拦截器
        initInterceptor();

        System.out.println("init success");
    }
    // 6.获取拦截器
    private void initInterceptor() {
        if (classNames.isEmpty()) return;

        try {
            Map<Integer, Interceptor> source = new TreeMap<>(Comparator.comparingInt(o -> o));

            for (String className : classNames) {
                Class<?> aClass = Class.forName(className);
                if (!Interceptor.class.isAssignableFrom(aClass)) {
                    continue;
                }
                int order = Integer.MIN_VALUE;
                if (aClass.isAnnotationPresent(Order.class)) {
                    order = aClass.getAnnotation(Order.class).value();
                }
                source.put(order, (Interceptor) aClass.newInstance());
            }
            for (Map.Entry<Integer, Interceptor> entry : source.entrySet()) {
                interceptorList.add(entry.getValue());
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    // 5.初始化method和url的对应关系
    private void initHandlerMapping() {
        if (iocMap.isEmpty()) return;

        for (String key : iocMap.keySet()) {
            Object obj = iocMap.get(key);
            Class<?> aClass = obj.getClass();
            if (!aClass.isAnnotationPresent(Controller.class)) continue;
            // 获取类定义的路径
            String baseUrl = "";
            if (aClass.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping annotation = aClass.getAnnotation(RequestMapping.class);
                baseUrl = annotation.value();
            }

            // 定义的方法
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(RequestMapping.class)) continue;

                RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                String methodUrl = annotation.value();
                String realUrl = baseUrl + methodUrl;
                if (!baseUrl.endsWith("/") && !methodUrl.startsWith("/")) {
                    realUrl = baseUrl + "/" +methodUrl;
                }

                // 每一个请求方法封装成Handler
                Handler handler = new Handler(obj, method, Pattern.compile(realUrl));
                Parameter[] parameters = method.getParameters();
                for(int i=0; i< parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    if (parameter.getType() == HttpServletRequest.class
                            || parameter.getType() == HttpServletResponse.class) {
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                    } else {
                        handler.getParamIndexMapping().put(parameter.getName(), i);
                    }
                }
                handlerMapping.add(handler);
            }
        }
    }
    // 4.依赖注入
    private void doAutowired() {
        if (iocMap.isEmpty())   return;

        for (String key : iocMap.keySet()) {
            Object obj = iocMap.get(key);
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(Autowired.class)) continue;

                Autowired annotation = field.getAnnotation(Autowired.class);
                String beanName = "".equals(annotation.value()) ?
                        StrUtils.lowerFirst(field.getType().getSimpleName()) : annotation.value();
                field.setAccessible(true);
                try {
                    field.set(obj, iocMap.get(beanName));
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    // 3.实例化
    private void doInstance() {
        if (classNames.isEmpty()) return;
        try {
            for (String className : classNames) {
                Class<?> aClass = Class.forName(className);

                if (aClass.isAnnotationPresent(Controller.class)) {
                    String simpleName = StrUtils.lowerFirst(aClass.getSimpleName());
                    Object instance = aClass.newInstance();
                    iocMap.put(simpleName, instance);
                }

                if (aClass.isAnnotationPresent(Service.class)) {
                    Service annotation = aClass.getAnnotation(Service.class);
                    String simpleName = "".equals(annotation.value()) ? StrUtils.lowerFirst(aClass.getSimpleName()) : annotation.value();
                    Object instance = aClass.newInstance();
                    iocMap.put(simpleName, instance);

                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class in: interfaces) {
                        iocMap.put(StrUtils.lowerFirst(in.getSimpleName()), instance);
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    // 2.扫描
    private void doScan(String scanPackage) {
        try {
            // 注意空格乱码
            URL resource = Thread.currentThread().getContextClassLoader().getResource("");
            String path = resource.toURI().getPath() + scanPackage.replaceAll("\\.", "/");
            File pack = new File(path);

            File[] files = pack.listFiles();
            for (File file : files) {
                if (file.isDirectory()) {
                    doScan(scanPackage + "." + file.getName());
                } else if(file.getName().endsWith(".class")) {
                    String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                    classNames.add(className);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    // 1.加载配置文件
    private void doLoadConfig(String configLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(configLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("cannot load conf:"+ configLocation);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doDispatch(req, resp);
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 根据url获取对应的Handler
        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }

        // 前置拦截器拦截器
        boolean interFlag;
        for (Interceptor interceptor : interceptorList) {
            interFlag = interceptor.preHandle(req, resp, handler);
            if (!interFlag) {
                resp.getWriter().write("用户无权限");
                return;
            }
        }

        // 反射执行
        Object returnValue = invokeForRequest(req, resp, handler);
        if (returnValue != null) {
            resp.getWriter().write(returnValue.toString());
        }

        // 结果试图对象的处理
        applyDefaultViewName();
        // 跳转页面：渲染试图
        processDispatchResult();
    }

    private void processDispatchResult() {
        System.out.println("processDispatchResult");
    }

    private void applyDefaultViewName() {
        System.out.println("applyDefaultViewName");
    }

    /**
     * 获取控制器
     * @param request
     * @return
     */
    private Handler getHandler(HttpServletRequest request) {
        if (handlerMapping.isEmpty()) return null;

        String contextPath = request.getContextPath();

        String requestURI = request.getRequestURI();
        requestURI = requestURI.substring(contextPath.length() + 1, requestURI.length());
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(requestURI);
            if (!matcher.matches()) continue;
            return handler;
        }
        return null;
    }

    /**
     * 执行具体的方法
     * @param req
     * @param resp
     * @param handler
     * @return
     */
    private Object invokeForRequest(HttpServletRequest req, HttpServletResponse resp, Handler handler) {
        // 参数绑定：数量+顺序
        Object[] paraValues = getMethodArgumentArgvs(req, resp, handler);

        // 反射执行方法
        Object invoke = null;
        try {
            Object controller = handler.getController();
            Method method = handler.getMethod();

            invoke = method.invoke(controller, paraValues);

            // 拦截器：业务逻辑执行完，尚未跳转页面
            Collections.reverse(interceptorList);
            for (Interceptor interceptor : interceptorList) {
                interceptor.postHandle(req, resp, handler);
            }

            // 页面渲染
            renderPage();
            // 拦截器：页面已经渲染
            for (Interceptor interceptor : interceptorList) {
                interceptor.afterCompletion(req, resp, handler);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return invoke;
    }

    private void renderPage() {
        System.out.println("渲染页面");
    }

    /**
     * 封装参数：数量+顺序
     * @param req
     * @param resp
     * @param handler
     * @return
     */
    private Object[] getMethodArgumentArgvs(HttpServletRequest req, HttpServletResponse resp, Handler handler) {
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();

        Object[] paraValues = new Object[parameterTypes.length];
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            // TODO 需要特殊处理，防止拿到的key为args*
            if (!handler.getParamIndexMapping().containsKey(param.getKey())) {
                continue;
            }
            // 处理数组情况type=1&type=2
            String value = StringUtils.join(param.getValue(), ",");
            Integer index = handler.getParamIndexMapping().get(param.getKey());
            paraValues[index] = value;
        }

        Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        if (requestIndex != null && requestIndex > 0) {
            paraValues[requestIndex] = req;
        }

        Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        if (responseIndex != null && responseIndex > 0) {
            paraValues[responseIndex] = resp;
        }
        return paraValues;
    }
}
